/*
 *  RenderingEngine.ES1.cpp
 *  VertexLighting
 *
 *  Created by BongjuKim on 11. 1. 30..
 *  Copyright 2011 thinkware. All rights reserved.
 *
 */

#ifdef PLATFORM_ANDROID
#include <math.h>
#include <android/log.h>

#include <lesson.h>
#include <common_def.h>
#else
#include <OpenGLES/ES1/gl.h>
#include <OpenGLES/ES1/glext.h>
#endif

#include "Interfaces.hpp"
#include "Matrix.hpp"

namespace VertexLighting
{

namespace ES1
{
	struct Drawable {
		GLuint VertexBuffer;
		GLuint IndexBuffer;
		int IndexCount;
	};
	
	class RenderingEngine : public IRenderingEngine
	{
	public:
		RenderingEngine();
		void Initialize(const vector<ISurface*>& surfaces);
		void Render(const vector<Visual>& visuals) const;
		void Render(const Visual* visual) const;
		
	private:
		vector<Drawable> m_drawables;
		GLuint m_colorRenderbuffer;
		GLuint m_depthRenderbuffer;
		mat4 m_translation;
	};
	
	
	IRenderingEngine* CreateRenderingEngine()
	{
		return new RenderingEngine();
	}
	
	RenderingEngine::RenderingEngine()
	{
		glGenRenderbuffersOES(1, &m_colorRenderbuffer);
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_colorRenderbuffer);
	}
	//
	void RenderingEngine::Initialize(const vector<ISurface*>& surfaces)
	{
		vector<ISurface*>::const_iterator surface;
		for (surface = surfaces.begin(); surface != surfaces.end(); ++surface) 
		{
			// Create the VBO for the vertices.
			vector<float> vertices;
			(*surface)->GenerateVertices(vertices, VertexFlagsNormals);
			GLuint vertexBuffer;
			glGenBuffers(1, &vertexBuffer);
			glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
			glBufferData(GL_ARRAY_BUFFER, 
						 vertices.size() * sizeof(vertices[0]),
						 &vertices[0],
						 GL_STATIC_DRAW);
			
			// Create a new VBO for the indices if needed.
			int indexCount = (*surface)->GetTriangleIndexCount();
			GLuint indexBuffer;
			if (!m_drawables.empty() && indexCount == m_drawables[0].IndexCount) {
				indexBuffer = m_drawables[0].IndexBuffer;
			} else {
				vector<GLushort> indices(indexCount);
				(*surface)->GenerateTriangleIndices(indices);
				glGenBuffers(1, &indexBuffer);
				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
				glBufferData(GL_ELEMENT_ARRAY_BUFFER, 
							 indexCount * sizeof(GLushort), 
							 &indices[0],
							 GL_STATIC_DRAW);
			}
			
			Drawable drawable = {vertexBuffer, indexBuffer, indexCount};
			m_drawables.push_back(drawable);
		}
		
		// Extract width and height from the color buffer.
		int width, height;
		glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &width);
		glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &height);
		
		// Create a depth buffer that has the same size as the color Buffer.
		glGenRenderbuffersOES(1, &m_depthRenderbuffer);
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_depthRenderbuffer);
		glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, width, height);
		
		// Create the framebuffer object.
		GLuint framebuffer;
		glGenFramebuffersOES(1, &framebuffer);
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, framebuffer);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, 
									 GL_COLOR_ATTACHMENT0_OES, 
									 GL_RENDERBUFFER_OES, 
									 m_colorRenderbuffer);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
									 GL_DEPTH_ATTACHMENT_OES, 
									 GL_RENDERBUFFER_OES, 
									 m_depthRenderbuffer);
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_colorRenderbuffer);
		
		// Set up various GL state.
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_NORMAL_ARRAY);
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
		glEnable(GL_DEPTH_TEST);
		
		// Set up the material properties.
		vec4 specular(0.5f, 0.5f, 0.5f, 1);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular.Pointer());
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);

		m_translation = mat4::Translate(0, 0, -7);

#ifdef PLATFORM_ANDROID
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
#endif
	}

	void RenderingEngine::Render(const vector<Visual>& visuals) const
	{
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		
		vector<Visual>::const_iterator visual = visuals.begin();
		for (int visualIndex = 0; visual != visuals.end(); ++visual, ++visualIndex) 
		{
			// Set the viewport transform.
			ivec2 size = visual->ViewportSize;
			ivec2 lowerLeft = visual->LowerLeft;
			glViewport(lowerLeft.x, lowerLeft.y, size.x, size.y);
			
			// Set the light position.
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
			vec4 lightPosition(0.25, 0.25, 1, 0);
			glLightfv(GL_LIGHT0, GL_POSITION, lightPosition.Pointer());
			// Set the GL_LIGHT_MODEL_TWO_SIDE only for Cone render.
			if (visualIndex == 0)
				glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
			else
				glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
			
			// Set the model-view transform.
			mat4 rotation = visual->Orientation.ToMatrix();
			mat4 modelview = rotation * m_translation;
			glLoadMatrixf(modelview.Pointer());
			
			// Set the projection transform.
			float h = 4.0f * size.y / size.x;
			mat4 projection = mat4::Frustum(-2, 2, -h / 2, h / 2, 5, 10);
			glMatrixMode(GL_PROJECTION);
			glLoadMatrixf(projection.Pointer());
			
			// Set the color.
			vec3 color = visual->Color * 0.75;
			vec4 diffuse(color.x, color.y, color.z, 1);
			glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.Pointer());
			
			// Draw the surface.
			int stride = 2 * sizeof(vec3);
			const GLvoid* normalOffset = (const GLvoid*) sizeof(vec3);
			const Drawable& drawable = m_drawables[visualIndex];
			glBindBuffer(GL_ARRAY_BUFFER, drawable.VertexBuffer);
			glVertexPointer(3, GL_FLOAT, stride, 0);
			glNormalPointer(GL_FLOAT, stride, normalOffset);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, drawable.IndexBuffer);
			glDrawElements(GL_TRIANGLES, drawable.IndexCount, GL_UNSIGNED_SHORT, 0);
		}
	}
	
	void RenderingEngine::Render(const Visual* visual) const
	{
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);

		glClear(GL_COLOR_BUFFER_BIT);
		
		// Set the viewport transform.
		ivec2 size = visual->ViewportSize;
		ivec2 lowerLeft = visual->LowerLeft;
		glViewport(lowerLeft.x, lowerLeft.y, size.x, size.y);
		
		// Set the model-view transform.
		mat4 rotation = visual->Orientation.ToMatrix();
		mat4 modelview = rotation * m_translation;
		glMatrixMode(GL_MODELVIEW);
		glLoadMatrixf(modelview.Pointer());
		
		// Set the projection transform.
		float h = 4.0f * size.y / size.x;
		mat4 projection = mat4::Frustum(-2, 2, -h / 2, h / 2, 5, 10);
		glMatrixMode(GL_PROJECTION);
		glLoadMatrixf(projection.Pointer());
		
		// Set the color.
		vec3 color = visual->Color;
		glColor4f(color.x, color.y, color.z, 1);
		
		// Draw the wireframe.
		int stride = sizeof(vec3);
		const Drawable& drawable = m_drawables[0];
		glBindBuffer(GL_ARRAY_BUFFER, drawable.VertexBuffer);
		glVertexPointer(3, GL_FLOAT, stride, 0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, drawable.IndexBuffer);
		glDrawElements(GL_LINES, drawable.IndexCount, GL_UNSIGNED_SHORT, 0);
	}

	

} // namespace ES1

} // namespace VertexLighting













